﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Streambolics
{
    public class ObservableObject
    {
        private WeakReference _Object;
        private string _Name;
        private string _Category;

        public ObservableObject (object aObject, string aName, string aCategory)
        {
            _Object = new WeakReference (aObject);
            _Name = aName;
            _Category = aCategory;
        }

        public ObservableObject (object aObject)
            : this (aObject, aObject.ToString(), "Observable")
        {
        }

        public bool IsObject (object aObject)
        {
            return _Object.IsAlive && _Object.Target==aObject;
        }

        public bool IsAlive
        {
            get
            {
                return _Object.IsAlive;
            }
        }

        public object Target
        {
            get
            {
                return _Object.Target;
            }
        }

        public string Name
        {
            get
            {
                return _Name;
            }
        }

        public string Category
        {
            get
            {
                return _Category;
            }
        }
    }

    public class ObservableObjectEventArgs : EventArgs
    {
        private ObservableObject _Object;

        public ObservableObjectEventArgs (ObservableObject aObject)
        {
            _Object = aObject;
        }

        public object Target
        {
            get
            {
                return _Object.Target;
            }
        }

        public ObservableObject ObservableObject
        {
            get
            {
                return _Object;
            }
        }
    }

    /// <summary>
    ///     The list of objects in the application that can be observed by the
    ///     user.
    /// </summary>
    /// <remarks><para>
    ///     Observable objects usually are features used by the kernel of the application
    ///     to perform some needed action. They do not represent the documents seen by the
    ///     user, but the plumbing beneath the application.
    /// </para><para>
    ///     Typical observable objects are computing modules, loggers, etc.
    /// </para><para>
    ///     The list of observable modules is very lightweight, simply listing the
    ///     objects that can <i>potentially</i> be observed by the user, but without
    ///     providing any way of actually inspecting them. It is the responsibility of
    ///     the application to enumerate the objects, and show them to the user.
    /// </para><para>
    ///     Although it is not required, the best approach for an application would
    ///     be to have a set of pluggable inspectors that can be selected according
    ///     to the type of the inspected object.
    /// </para></remarks>

    public static class ObservableObjects
    {
        private static List<ObservableObject> _Objects = new List<ObservableObject> ();
        private static bool _Cleanup = false;

        /// <summary>
        ///     Add an object to the list of inspectable objects.
        /// </summary>
        /// <param name="o">
        ///     The object to add to the list.
        /// </param>
        /// <remarks><para>
        ///     The object is added using a weak reference, so that its
        ///     presence in the list of inspectable objects will not prevent
        ///     it from being garbage collected.
        /// </para><para>
        ///     It is not an error to add an object multiple times. It will
        ///     however be listed only once.
        /// </para></remarks>

        public static void Add (object o)
        {
            Cleanup ();
            if (!Contains (o))
            {
                InternalAdd (new ObservableObject (o));
            }
        }

        public static void Add (object o, string n, string c)
        {
            Cleanup ();
            if (!Contains (o))
            {
                InternalAdd (new ObservableObject (o, n, c));
            }
        }

        public static event EventHandler<ObservableObjectEventArgs> ObjectAdded;

        private static void InternalAdd (ObservableObject oo)
        {
            _Objects.Add (oo);
            if (ObjectAdded != null)
            {
                ObjectAdded (null, new ObservableObjectEventArgs (oo));
            }
        }

        public static void Remove (object o)
        {
            List<ObservableObject> toremove = new List<ObservableObject> ();

            foreach (var v in _Objects)
            {
                if (!v.IsAlive || v.Target == o)
                {
                    toremove.Add (v);
                }
            }

            foreach (var v in toremove)
            {
                InternalRemove (v);
            }
            _Cleanup = false;
        }

        public static event EventHandler<ObservableObjectEventArgs> ObjectRemoved;

        private static void InternalRemove (ObservableObject oo)
        {
            _Objects.Remove (oo);
            if (oo.IsAlive && ObjectRemoved != null)
            {
                ObjectRemoved (null, new ObservableObjectEventArgs (oo));
            }
        }


        public static bool Contains (object o)
        {
            return Objects.Contains (o);
        }

        public static IEnumerable<object> Objects
        {
            get
            {
                Cleanup ();
                foreach (var v in _Objects)
                {
                    object o = v.Target;
                    if (o != null)
                    {
                        yield return o;
                    }
                    else
                    {
                        _Cleanup = true;
                    }
                }
            }
        }

        public static IEnumerable<ObservableObject> All
        {
            get
            {
                Cleanup ();
                foreach (var v in _Objects)
                {
                    object o = v.Target;
                    if (o != null)
                    {
                        yield return v;
                    }
                    else
                    {
                        _Cleanup = true;
                    }
                }
            }
        }

        private static void Cleanup ()
        {
            if (_Cleanup)
            {
                Remove (null);
            }
        }
    }
}
